package nancal.mp.mvc.ops

import nancal.mp.db.mongo.K8sYamlType
import nancal.mp.db.mongo.extend.getAppDeploySetting
import nancal.mp.db.mongo.mor
import nancal.mp.service.K8sBaseService
import nancal.mp.service.K8sConfigMapService
import nancal.mp.service.K8sDeploymentService
import nancal.mp.service.K8sService
import nancal.mp.util.*
import nbcp.base.annotation.Require
import nbcp.base.comm.*
import nbcp.base.extend.*
import nbcp.base.json.*
import nbcp.base.utils.ClassUtil
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*
import java.io.File
import java.io.IOException
import java.net.URI
import java.net.URL
import java.net.URLDecoder
import java.util.*
import java.util.jar.JarFile
import javax.servlet.http.HttpServletRequest
import kotlin.Throws

@RequestMapping("/ops/k8s/service")
@RestController
class K8sServiceController {
    companion object {
        private val logger = LoggerFactory.getLogger(this::class.java.declaringClass)
    }

    @Autowired
    lateinit var k8sDeployment: K8sDeploymentService;

    @Autowired
    lateinit var k8sService: K8sService

    @GetMapping("/test")
    fun test(): String {
        var client = K8sBaseService.getK8sClient("")

        var ret = "";
        var apiGroupVersion = "apps/v1";
        val parts: Array<String> = apiGroupVersion.split("/").toTypedArray()
        val apiGroup: String
        val apiVersion: String
        if (parts.size == 1) {
            apiGroup = ""
            apiVersion = apiGroupVersion
        } else {
            apiGroup = parts[0]
            apiVersion = parts[1]
        }

        ret += "apiGroup:" + apiGroup + "\n";
        ret += "apiVersion: " + apiVersion + "\n";

        ret += "----" + "\n";
        ret += "getClassNamesFromPackage:" + "\n";

        var cs = ClassUtil.findClasses("io.kubernetes.client.openapi.models.**")

        if (cs == null) {
            ret += "null"
        } else {
            ret += cs.joinToString("\n")
        }

        ret += "----" + "\n";

//        var gvk = ModelMapper::class.java.getDeclaredField("preBuiltClassesByGVK");
//        gvk.isAccessible = true;
//        var v = gvk.get(null) as Map<GroupVersionKind, Class<*>>;
//        ret += "preBuiltClassesByGVK:" + "\n";
//
//        ret += v.size.toString() + "\n";
//        ret += v.keys.map { it.kind.AsString() + ":" + it.group.AsString() + "/" + it.version }.joinToString("\n")
        return ret;
    }


    @Throws(IOException::class)
    private fun getClassNamesFromPackage(classLoader: ClassLoader, pkg: String): List<String> {
        val packageURL: URL
        val names = ArrayList<String>()
        val packageName = pkg.replace(".", "/")
        packageURL = classLoader.getResource(packageName)
        if (packageURL.protocol == "jar") {
            var jarFileName: String
            val jf: JarFile
            var entryName: String
            jarFileName = URLDecoder.decode(packageURL.file, "UTF-8")
            jarFileName = jarFileName.substring(5, jarFileName.indexOf("!"))
            logger.warn("Loading classes from jar {}", jarFileName)
            jf = JarFile(jarFileName)
            var jarEntries = jf.entries().toList()

            println(jarEntries.map { it.name }.joinToString("\n"))

            jarEntries.forEach {
                entryName = it.name
                if (entryName.startsWith(packageName) && entryName.length > packageName.length + 5) {
                    entryName = entryName.substring(packageName.length + 1, entryName.lastIndexOf('.'))
                    names.add("$pkg.$entryName")
                }
            }
            jf.close()
        } else {
            val uri = URI.create(packageURL.toString())
            val folder = File(uri.path)
            val contenuti = folder.listFiles()
            if (contenuti == null) {
                logger.warn("No files to load found in {}", folder.path)
                return names
            }
            var entryName: String
            for (actual in contenuti) {
                entryName = actual.name
                entryName = entryName.substring(0, entryName.lastIndexOf('.'))
                names.add("$pkg.$entryName")
            }
        }
        return names
    }

    @PostMapping("/deploy")
    fun deploy(
        targetEnv: String,
        @Require namespace: String,
        @Require name: String,
        type: K8sYamlType,
        @RequestBody yaml: String
    ): JsonResult {
        if (type == K8sYamlType.Deployment) {
            return k8sDeployment.applyDeploymentYaml(targetEnv, namespace, name, yaml);
        } else if (type == K8sYamlType.Service) {
            return k8sService.applyServiceYaml(targetEnv, namespace, name, yaml);
        }

        throw RuntimeException("不识别的类型:${type}")
    }


    @PostMapping("/apply-service-yaml")
    fun apply(
        targetEnv: String,
        @RequestBody yaml: String,
        @Require namespace: String,
        @Require name: String
    ): JsonResult {
        return k8sService.applyServiceYaml(targetEnv, namespace, name, yaml);
    }


    @RequestMapping("/get-service-yaml", method = arrayOf(RequestMethod.GET, RequestMethod.POST))
    fun get(targetEnv: String, @Require namespace: String, @Require name: String): ApiResult<String> {
        k8sService.getServiceYaml(targetEnv, namespace, name)
            .apply {
                if (this.HasValue) {
                    return ApiResult.of(this)
                }
            }

        return ApiResult.error("找不到服务 ${namespace}.${name}")
    }

    @Autowired
    lateinit var configMapService: K8sConfigMapController

    @Autowired
    lateinit var nacosController: NacosController;

//    @Deprecated("使用configmapService的Url")
//    @RequestMapping("/set-deployed-nginx-configmap", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
//    fun setDeployedService(targetEnv: String, @Require namespace: String): JsonResult {
//        configMapService.setNginxConfigmap(targetEnv, namespace)
//                .apply {
//                    if (this.msg.HasValue) {
//                        return this;
//                    }
//                }
//
//        return JsonResult()
//    }

    /**
     * 部署一级网关， 不会删除 configmap , 但是会删除 deployment！
     */
    @RequestMapping("/deploy-gateway", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
    fun deployGateway(targetEnv: String, @Require namespace: String): ApiResult<String> {
        if (namespace == "mp") {
            return ApiResult.error("中台网关服务必须手工初始化");
        }


        sync_java_ms_between_ns(targetEnv, "mp", namespace, "mp-gateway-api", namespace + "-gateway-api")
        return ApiResult();
    }

//    @RequestMapping("/get-ext-services", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
//    fun getDeployExt(): ListResult<String> {
//        mor.dev.externalService.query()
//                .whereWithAppGroup(config.appGroup)
//                .where { it.targetNamespace.mongoIsNullOrEmpty() }
//                .where { it.targetService.mongoIsNullOrEmpty() }
//                .toList()
//                .map { it.name }
//                .toSet()
//                .apply {
//                    return ListResult.of(this)
//                }
//    }

//    @RequestMapping("/deploy-ext", method = arrayOf(RequestMethod.POST, RequestMethod.GET))
//    fun deployExt(targetEnv: String, @Require namespace: String): ApiResult<String> {
//        //部署 external
//        mor.dev.externalService.query()
//                .whereWithAppGroup(config.appGroup)
//                .where { it.targetNamespace.mongoIsNullOrEmpty() }
//                .where { it.targetService.mongoIsNullOrEmpty() }
//                .toList()
//                .distinctBy { it.name }
//                .forEach {
//                    k8sService.createExternalService(
//                            targetEnv,
//                            namespace,
//                            it.name,
//                            it.targetNamespace.AsString("default"),
//                            it.targetService.AsString(it.name)
//                    )
//                }
//
//        return ApiResult();
//    }


    @RequestMapping("/sync-between-ns", method = arrayOf(RequestMethod.POST))
    fun sync_java_ms_between_ns(
        targetEnv: String,
        @Require fromNamespace: String,
        @Require targetNamespace: String,
        @Require fromMsName: String,
        @Require targetMsName: String
    ): JsonResult {
        var fromEnv = "";
//        var appDeploySetting = mor.dev.appDeploySetting.currentAppDeploySetting;

        k8sConfigMapService.get(fromEnv, fromNamespace, fromMsName).data!!.forEach {
            var key = it.key;
            var value = it.value;
            k8sConfigMapService.setContent(targetEnv, targetNamespace, targetMsName, key, value)
        }


        if (k8sDeployment.existsDeployment(targetEnv, targetNamespace, targetMsName + "-java")) {
            k8sDeployment.delete(targetEnv, targetNamespace, targetMsName + "-java")
        }

        if (k8sService.existsService(targetEnv, targetNamespace, targetMsName + "-java").data
                ?: false
        ) {
            k8sService.deleteService(targetEnv, targetNamespace, targetMsName + "-java")
        }


        var deploymentMap = k8sDeployment.getDeploymentYaml(fromEnv, fromNamespace, fromMsName + "-java")

        if (deploymentMap.isEmpty()) {
            return JsonResult.error("找不到 k8s ${fromNamespace}.${fromMsName}-java DeploymentYaml 内容");
        }

        deploymentMap.tidyK8sYaml();
        deploymentMap.setNamespace(targetNamespace)
        deploymentMap.setEnvParameter("APP_GROUP", targetNamespace)
        deploymentMap.setEnvParameter("APP_NAME", targetMsName)
        deploymentMap.setName(targetMsName + "-java")

        k8sDeployment.applyDeploymentYaml(
            targetEnv,
            targetNamespace,
            targetMsName + "-java",
            deploymentMap.ToYaml(),
            ""
        )
            .apply {
                if (this.msg.HasValue) {
                    return this;
                }
            }


        var serviceYaml = k8sService.getServiceYaml(fromEnv, fromNamespace, fromMsName + "-java");
        if (serviceYaml.isEmpty()) {
            return JsonResult.error("找不到 k8s ${fromNamespace}.${fromMsName}-java ServiceYaml 内容");
        }
        var serviceMap = serviceYaml.FromYamlText<JsonMap>()
        serviceMap.tidyK8sYaml();
        serviceMap.setNamespace(targetNamespace)
        serviceMap.setEnvParameter("APP_GROUP", targetNamespace)
        serviceMap.setName(targetMsName + "-java")

        k8sService.applyServiceYaml(targetEnv, targetNamespace, targetMsName + "-java", serviceMap.ToYaml())
            .apply {
                if (this.msg.HasValue) {
                    return this;
                }
            }

        return JsonResult()
    }


    @Autowired
    lateinit var k8sConfigMapService: K8sConfigMapService

    @PostMapping("/create-ns")
    fun createNs(
        targetEnv: String,
        @Require namespace: String,
        request: HttpServletRequest
    ): JsonResult {
        k8sService.createNamespace(targetEnv, namespace);
        return JsonResult()
    }


    @PostMapping("/set-ns-configmap-role")
    fun createNsRole(
        targetEnv: String,
        @Require namespace: String,
        request: HttpServletRequest
    ): JsonResult {
        var name = "default-clusterrolebinding-${namespace}"
        var yml = """
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ${name}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: admin
subjects:
  - kind: ServiceAccount
    name: default
    namespace: ${namespace}
"""
        k8sService.applyClusterRoleBinding(targetEnv, name, yml);
        return JsonResult()
    }


//    @PostMapping("/deploy-target-api")
//    fun deploy_target_api(
//        targetEnv: String,
//        @Require namespace: String,
//        @Require targetNs: String,
//        request: HttpServletRequest
//    ): JsonResult {
//        k8sService.createExternalService(targetEnv, namespace, "${targetNs}-api", targetNs, "mp-gateway-api-java");
//        return JsonResult()
//    }

    @PostMapping("/deploy-endpoint")
    fun deploy_ep(
        targetEnv: String,
        @Require sourceNamespace: String,
        @Require namespace: String,
        @Require name: String,
        @Require port: Int,
        request: HttpServletRequest
    ): JsonResult {
        k8sService.createEndpointWithService(targetEnv, namespace, name, sourceNamespace, port);
        return JsonResult()
    }

    @PostMapping("/delete-endpoint")
    fun delete_ep(
        targetEnv: String,
        @Require namespace: String,
        @Require name: String,
        request: HttpServletRequest
    ): JsonResult {
        k8sService.deleteEndpointWithService(targetEnv, namespace, name);
        return JsonResult()
    }

    @PostMapping("/deploy-external")
    fun deploy_ext(
        targetEnv: String,
        @Require namespace: String,
        @Require name: String,
        @Require sourceNamespace: String,
        @Require sourceName: String,
        request: HttpServletRequest
    ): JsonResult {
        k8sService.createExternalService(targetEnv, namespace, name, sourceNamespace, sourceName);
        return JsonResult()
    }

    /**
     * 重启的是 Deployment，不是Service
     */
    @PostMapping("/restart")
    fun restart(
        targetEnv: String,
        @Require namespace: String,
        @Require serviceName: String,
        request: HttpServletRequest
    ): JsonResult {
        k8sDeployment.restart(targetEnv, namespace, serviceName)
        return JsonResult()
    }


    fun getNginxDeployYaml(nginxImageName: String) = """
apiVersion: "apps/v1"
kind: "Deployment"
metadata:
  annotations:
    k8s.kuboard.cn/ingress: "false"
    k8s.kuboard.cn/service: "NodePort"
    k8s.kuboard.cn/workload: "nginx"
  labels:
    k8s.kuboard.cn/layer: "db"
    k8s.kuboard.cn/name: "nginx"
  name: "nginx"
spec:
  selector:
    matchLabels:
      name: "nginx"
  template:
    metadata:
      labels:
        name: "nginx"
    spec:
      containers:
      - image: "${nginxImageName}"
        imagePullPolicy: "IfNotPresent"
        name: "nginx"
        env:
        - name: restart_version
          value: 1
        ports:
        - containerPort: 80
          name: "tcp"
          protocol: "TCP"
        terminationMessagePath: "/dev/termination-log"
        terminationMessagePolicy: "File"
        volumeMounts:
        - mountPath: "/etc/nginx/conf.d"
          name: "nginxconf"
        - mountPath: "/data/ng-pem"
          name: "ngpem"
          readOnly: true
        - mountPath: "/data/ng-file"
          name: "file"
          readOnly: true
      dnsPolicy: "ClusterFirst"
      imagePullSecrets:
      - name: "myregistrykey"
      initContainers:
      - command:
        - "sh"
        - "-c"
        - "echo 10000 > /proc/sys/net/core/somaxconn"
        image: "busybox"
        imagePullPolicy: "IfNotPresent"
        name: "init"
        securityContext:
          privileged: true
        terminationMessagePath: "/dev/termination-log"
        terminationMessagePolicy: "File"
      restartPolicy: "Always"
      schedulerName: "default-scheduler"
      terminationGracePeriodSeconds: 30
      volumes:
      - configMap:
          defaultMode: 420
          name: "nginxconf"
        name: "nginxconf"
      - hostPath:
          path: "/data/ng-pem"
          type: "Directory"
      - hostPath:
          path: "/data/ng-file"
          type: "Directory"
        name: "ngpem"
"""


    private fun getNginxServiceYaml(noePort: Int): String {
        return """
apiVersion: "v1"
kind: "Service"
metadata:
  annotations:
    k8s.kuboard.cn/workload: "nginx"
  labels:
    name: "nginx"
  name: "nginx"
spec:
  externalTrafficPolicy: "Cluster"
  internalTrafficPolicy: "Cluster"
  ipFamilies:
  - "IPv4"
  ipFamilyPolicy: "SingleStack"
  ports:
  - name: "http"
    nodePort: 80
    port: 80
    protocol: "TCP"
    targetPort: 80
  - name: "https"
    nodePort: 443
    port: 443
    protocol: "TCP"
    targetPort: 443
  selector:
    name: "nginx"
  sessionAffinity: "None"
  type: "NodePort"
"""
    }


    @PostMapping("/deploy-nginx")
    fun initNginx(
        targetEnv: String,
        @Require namespace: String,
        request: HttpServletRequest
    ): JsonResult {
        var clusterInfo = mor.dev.clusterInfo.queryByCode(namespace).toEntity()

        if (clusterInfo == null) {
            return JsonResult.error("找不到数据")
        }

        var setting = mor.dev.appDeploySetting.getAppDeploySetting(targetEnv);

        var nginxDeployJson =
            getNginxDeployYaml(setting.getOpsNginxImage()).FromYamlText<JsonMap>()
        nginxDeployJson.tidyK8sYaml()
        nginxDeployJson.setNamespace(namespace)
        nginxDeployJson.setReplicasAtLeast1()

        k8sDeployment.applyDeploymentYaml(targetEnv, namespace, "nginx", nginxDeployJson.ToYaml())
            .apply {
                if (this.msg.HasValue) {
                    return this;
                }
            }


        var nginxServiceJson =
            getNginxServiceYaml(800 + clusterInfo.index).FromYamlText<JsonMap>()

        nginxServiceJson.tidyK8sYaml()
        nginxServiceJson.setNamespace(namespace)
        k8sService.applyServiceYaml(targetEnv, namespace, "nginx", nginxServiceJson.ToYaml())
            .apply {
                if (this.msg.HasValue) {
                    return this;
                }
            }

        return JsonResult();
    }

}